package com.echatim.broker;

import com.broker.base.IBrokerEventBus;
import com.broker.base.IBrokerStorage;
import com.broker.base.protocol.ProtocolMessage;
import com.broker.base.protocol.request.RequestMessage;
import com.broker.base.utils.ObjectUtils;
import com.broker.hook.BrokerLinkableHook;
import com.broker.hook.support.ClientSingleHook;
import com.broker.utils.events.EventFactory;
import com.broker.utils.strorage.StorageFactory;
import com.commom.Topic;
import com.commom.TopicProfessionalMock;
import com.corundumstudio.socketio.SocketIOServer;
import com.echatim.ApplicationWrapper;
import com.echatim.broker.localsvc.TopicDispatcher;
import com.echatim.broker.localsvc.TopicSender;
import com.event.EventBus;
import com.utils.Beans;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * @author kong <androidsimu@163.com>
 * create by 2019/3/12 13:52
 * Description: echatimbroker
 **/

@ConditionalOnProperty(name="echatim.sdk.auth-type", havingValue="community")
@Component
public class SocketIOBroker implements ApplicationListener<ApplicationReadyEvent> {
    @Autowired
    private EventBus eventBus;
    @Autowired
    private SocketIOServer socketIOServer;
    @Autowired
    TopicDispatcher topicDispatcher;
    @Autowired
    TopicSender topicSender;
    @Value("${broker.machineId}")
    private String machineId;
    @Autowired
    private BrokerConfig brokerConfig;

//    @PostConstruct
//    private void autoStartup() {
//        eventBus.register(this);
//        startSocketIOBroker(socketIOServer, topicDispatcher, brokerConfig);
//    }
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        if(!ApplicationWrapper.contextReady()){
            ApplicationWrapper.setContext(event.getApplicationContext());
        }
        eventBus.register(this);
        startSocketIOBroker(socketIOServer, topicDispatcher, brokerConfig);
    }

    @PreDestroy
    private void autoStop() {
        socketIOServer.stop(); // 在销毁 Bean之前关闭,避免重启项目服务端口占用问题
        EventFactory.getInstance().destroy(); // 销毁事件组件
    }

    public void destroy() {
        this.autoStop();
    }

    private void startSocketIOBroker(SocketIOServer socketIOServer,
                                     TopicDispatcher topicDispatcher,
                                     BrokerConfig brokerConfig){
        // broker 组件列表, 可根据需要自行添加组件
        AppBrokerHook appBrokerHook = ApplicationWrapper.getContext().getBean(AppBrokerHook.class);
        LinkedList<BrokerLinkableHook> hooks = new LinkedList<BrokerLinkableHook>(){{
            add(appBrokerHook);
            add(new ClientSingleHook(machineId)); // Client转发组件(分发到实际连接client 的机器)
        }};
        LinkedList<BrokerLinkableHook> hooksLinkable = this.createBrokerLinkableHooks(hooks);
        // 配置broker
//        BrokerConfig brokerConfig = new BrokerConfig();
        if(Beans.strEmpty(brokerConfig.getMqPoint())){
            brokerConfig.setMqPoint(machineId);
        }
        brokerConfig.initSystemProperty();
        // 设置用户的认证信息
        IBrokerStorage storage = StorageFactory.getInstance();
        IBrokerEventBus eventBus = EventFactory.getInstance();
        // 配置中间件
        configBroker(socketIOServer, hooksLinkable, storage);
        // 启动socket.io server
        socketIOServer.start();
    }

    // 事件通知
    private IBrokerEventBus createBrokerEventBus(){
        return EventFactory.getInstance();
    }

    // 配置中间件
    public void configBroker(SocketIOServer socketIOServer, LinkedList<BrokerLinkableHook> hooks, IBrokerStorage storage){
        IBrokerEventBus eventBus = createBrokerEventBus();
        // 组件初始化
        hooks.forEach(h->{
            h.startup(socketIOServer, storage, eventBus);
        });
        // 监听客户端连接
        socketIOServer.addConnectListener(client -> {
            hooks.forEach(e->{
                e.onConnected(client);
            });
        });
        // 监听客户端断开连接
        socketIOServer.addDisconnectListener(client -> {
            hooks.forEach(e->{
                e.onDisConnected(client);
            });
        });
        // 所有的topic
        List<String> listenTopics = new ArrayList<>();
        listenTopics.addAll(Topic.listenTopics);
        listenTopics.addAll(TopicProfessionalMock.listenTopics); // 加入专业的监听topic
        listenTopics.forEach(e->{
            socketIOServer.addEventListener(e, Object.class, (client, msgBody, ackSender) -> {
                ProtocolMessage message = ObjectUtils.copy(msgBody, ProtocolMessage.class);
                message.setClientId(client.getSessionId().toString());
                if(message != null){
                    if(hooks.size() > 0){
                        // 使用责任链模式, 将onReceiveMessage 依次向后传递
                        hooks.get(0).onReceiveMessage(
                                client,
                                new RequestMessage<>()
                                        .setTopic(e)
                                        .setMethod(message.getMethod())
                                        .setRequestId(message.getRequestId())
                                        .setBody(msgBody)
                                        .setProtocolMessage(message)
                                ,ackSender
                        );
                    }
                }
            });
        });
    }


    // 将组件一个接一个连接起来
    private LinkedList<BrokerLinkableHook> createBrokerLinkableHooks(LinkedList<BrokerLinkableHook> hooks){
        for(int i = 0; i < hooks.size(); i++){
            if( i == hooks.size()-1 ){
                hooks.get(i).setNext(null); // 没有后续组件
            }
            else {
                hooks.get(i).setNext(hooks.get(i+1));
            }
        }
        return hooks;
    }


}
